package lv.senan.algorithms.turney;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import lv.senan.clients.bing.BingClient;
import lv.senan.morphology.WordProperties;
import lv.senan.morphology.WordText;

/**
 * Implementation of Peter Turney's "Thumb Up or Thumbs Down" algorithm.
 * 
 * @see http://acl.ldc.upenn.edu/acl2002/MAIN/pdfs/Main425.pdf
 * @author Igors Gulbinskis
 */
public class TurneyAlgorithm {
	
	private static Logger logger = Logger.getLogger(TurneyAlgorithm.class.getName());
	
	private BingClient    bingClient;	
	private TextProcessor textProcessor;
	
	public TurneyAlgorithm() {
		this.bingClient = new BingClient();
		this.textProcessor = new TextProcessor();
	}

	/**
	 * Calculates the semantic orientation for partof speech.
	 */
	private double getSemanticOrientation(String phrase, String positiveReference, String negativeReference) throws Exception {
		String r1 = bingClient.buildRequest(positiveReference);
		String r2 = bingClient.buildRequest(negativeReference);
		String r3 = bingClient.buildRequest(phrase + " " + positiveReference);
		String r4 = bingClient.buildRequest(phrase + " " + negativeReference);
		
		long h1 = bingClient.getResultsCount(bingClient.buildDocument(r1));
		logger.log(Level.FINEST, "count of \"" + positiveReference + "\" = " + h1);
		long h2 = bingClient.getResultsCount(bingClient.buildDocument(r2));
		logger.log(Level.FINEST, "count of \"" + negativeReference + "\" = " + h2);
		long h3 = bingClient.getResultsCount(bingClient.buildDocument(r3));
		logger.log(Level.FINEST, "count of \"" + phrase + "\" NEAR \"" + positiveReference + "\" = " + h3);
		long h4 = bingClient.getResultsCount(bingClient.buildDocument(r4));
		logger.log(Level.FINEST, "count of \"" + phrase + "\" NEAR \"" + negativeReference + "\" = " + h4);
		
		double result = 0;
		if (h1 * h4 != 0) {
			result = Math.log((double) (h3 * h2) / (double) (h1 * h4));
			if (result != 0) {
				 result = result + AlgorithmConstants.C;
			}
		}
		logger.info("semantic orientation for phrase \"" + phrase + "\" = " + result);
		return result;
	}	
	
	/**
	 * Calculates the average semantic orientation for the whole text.
	 */
	public SemanticOrientationResult getSemanticOrientation(String str, boolean corpusUsed) {				
		
		WordText text = textProcessor.process(str, corpusUsed);						
		logger.info(text.toString());
		
		List<String> phrases = extractPhrases(text, AlgorithmConstants.SEARCH_TEMPLATES);
		
		List<String> neutralPhrases  = new ArrayList<String>();
		List<String> positivePhrases = new ArrayList<String>();
		List<String> negativePhrases = new ArrayList<String>();		
		
		List<Double> positiveSemanticOrientations = new ArrayList<Double>();
		List<Double> negativeSemanticOrientations = new ArrayList<Double>();
		
		double total = 0;
		double so;
		for (String phrase : phrases) {
			logger.log(Level.FINEST, "current phrase = " + phrase);
			try {
				so = getSemanticOrientation(
					phrase,
					AlgorithmConstants.POSITIVE_REFERENCE,
					AlgorithmConstants.NEGATIVE_REFERENCE
				);
				total += so;
				
				if (so < 0) {
					if (!negativePhrases.contains(phrase)) {
						negativePhrases.add(phrase);
						negativeSemanticOrientations.add(so);
					}
				} else if (so > 0) {
					if (!positivePhrases.contains(phrase)) {
						positivePhrases.add(phrase);
						positiveSemanticOrientations.add(so);
					}					
				} else {
					if (!neutralPhrases.contains(phrase)) {
						neutralPhrases.add(phrase);
					}
				}				
				
				logger.log(Level.FINEST, "semantic orientation = " + so);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		SemanticOrientationResult result = new SemanticOrientationResult();		
		result.setEvaluation(phrases.size() == 0 ? 0 : (double) total / phrases.size());
		result.setNeutralPhrases(neutralPhrases.toArray(new String[0]));
		result.setPositivePhrases(positivePhrases.toArray(new String[0]));
		result.setPositiveSemanticOrientations(positiveSemanticOrientations.toArray(new Double[0]));
		result.setNegativePhrases(negativePhrases.toArray(new String[0]));
		result.setNegativeSemanticOrientations(negativeSemanticOrientations.toArray(new Double[0]));
		
		return result;
	}
	
	private boolean isExclusion(String word) {
		for (String exclusion : AlgorithmConstants.EXCLUSION) {
			if (exclusion.equals(word.toLowerCase())) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Extracts template sentence constructions from text.
	 */
	private List<String> extractPhrases(WordText text, int template[][]) {
		List<String> result = new ArrayList<String>();
		
		String  phrase;		
		boolean matches;
		next: for (int i = 0; i < text.size(); i++) {			
			for (int j = 0; j < template.length; j++) {
				phrase = "";
				matches = true;
				
				int p = 0;
				for (int k = 0; k < template[j].length; k++) {
					if (i + k < text.size()) {
						if (!isExclusion(text.get(i + k).getCharacters())) {	
							if (text.get(i + k).getType() == template[j][k]) {
								phrase += (phrase.length() > 0 ? " " : "") + text.get(i + k).getCharacters();
								p++;
							} else if (template[j][k] == AlgorithmConstants.NOT_NOUN
								&& text.get(i + k).getType() != WordProperties.NOUN) {
								// phrase += (k > 0 ? " " : "") + text.get(i + k).getCharacters();
								p++;
							} else if (template[j][k] == AlgorithmConstants.NOT_ADJECTIVE
								&& text.get(i + k).getType() != WordProperties.ADJECTIVE) {
								// phrase += (k > 0 ? " " : "") + text.get(i + k).getCharacters();
								p++;
							} else if (template[j][k] == AlgorithmConstants.NOT_ADVERB
								&& text.get(i + k).getType() != WordProperties.ADVERB) {
								// phrase += (k > 0 ? " " : "") + text.get(i + k).getCharacters();
								p++;
							} else {
								matches = false;
							}
						} else {
							matches = false;
						}
					} else {
						matches = false;
					}
				}
				if (matches) {
					result.add(phrase);
					i += p;
					continue next;
				}				
			}
		}
		return result;		
	}
	
}
